package com.cy.jt.system.service.aspect;

import com.cy.jt.common.annotaion.RequiredLog;
import com.cy.jt.system.domain.SysLog;
import com.cy.jt.system.service.SysLogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;

/* Spring AOP 中基于@Aspect注解描述切面类型,切面类型中可以定义多个
   切入点和通知方法
   1)切入点:要执行扩展业务的一些方法集合(这些方法通常会认为是切入点方法)
   2)通知方法:用于封装扩展业务逻辑的方法
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
@Aspect
@Component
public class SysLogAspect {
    private static final Logger log=
            LoggerFactory.getLogger(SysLogAspect.class);
    /*@Pointcut 注解用于定义切入点,@annotation为一个切入点表达式,表达式中注解
       * 描述的方法为切入点方法*/
       @Pointcut("@annotation(com.cy.jt.common.annotaion.RequiredLog)")
       public void doLog(){}//doLog方法没有意义,只是用于承载上面的注解

       /*
        @Around注解描述的方法为通知方法,此方法内部可以直接调用目标方法(就是要织入
        扩展功能的方法),并且可以在目标方法执行前后添加扩展业务
        joinPoint 为连接点对象,ProceedingJoinPoint类型只能应用在
        @Around注解描述的方法参数中,
        */
       @Around("doLog()")
       public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable{

           long t1=System.currentTimeMillis();
           log.debug("around.before {}",t1);
           try {
               Object result = joinPoint.proceed();//去执行目标方法
               long t2 = System.currentTimeMillis();
               log.debug("around.after {}", t2);
               doLogInfo(joinPoint,t2-t1,null);
               return result;
           }catch (Throwable e){
               e.printStackTrace();
               log.error("exception {}",e.getMessage());
               long t2=System.currentTimeMillis();
               doLogInfo(joinPoint,t2-t1,e);
               throw e;
           }
       }

       /*
        获取用户的详细操作日志:谁(ip+username)在什么时间点执行了什么操作,
        访问了哪个方法,传递了什么参数.耗时多长.
        */
       private void doLogInfo(ProceedingJoinPoint joinPoint,
                              long time,
                              Throwable e) throws JsonProcessingException, NoSuchMethodException {
          //1.获取用户操作日志
           //1.1获取ip地址(暂时没有时,先用假数据替换)
           String ip="192.168.126.128";
           //1.2获取登录用户名(暂时没有时,先用假数据替换)
           String username="tony";
           //1.3获取具体操作(定义在了RequiredLog注解中)
           //1.3.1获取目标类型(代理对象的目标)
           Class<?> targetCls=joinPoint.getTarget().getClass();
           //1.3.2获取目标方法(类,方法名,参数列表)
           MethodSignature methodSignature=
                   (MethodSignature) joinPoint.getSignature();
           //Method targetMethod=methodSignature.getMethod();//假如目标类实现了接口,则这种方式默认获取接口中的方法
           String methodName=methodSignature.getName();
           Method targetMethod=targetCls.getMethod(methodName,
                      methodSignature.getParameterTypes());
           //1.3.3获取方法上的RequiredLog注解
           RequiredLog requiredLog=targetMethod.getAnnotation(RequiredLog.class);
           //1.3.4获取方法上的操作名
           String operation=requiredLog.operation();
           //1.4获取方法声明信息
           String targetClassMethodName=
                   targetCls.getName()+"."+methodName;//类全名.方法
           //1.5获取方法执行时的实际参数,并将其转换为json字符串
           String params=new ObjectMapper().writeValueAsString(joinPoint.getArgs());
          //2.封装操作日志(pojo对象)
           SysLog sysLog=new SysLog();
           sysLog.setIp(ip);
           sysLog.setUsername(username);
           sysLog.setCreatedTime(new Date());
           sysLog.setOperation(operation);
           sysLog.setMethod(targetClassMethodName);
           sysLog.setParams(params);
           if(e!=null) {
               sysLog.setError(e.getMessage());
               sysLog.setStatus(0);
           }
           sysLog.setTime(time);
          //3.输出或记录操作日志
           String logJsonStr=
           new ObjectMapper().writeValueAsString(sysLog);
           log.info("log info {}",logJsonStr);
           //后续将日志写入到数据库
           sysLogService.insertLog(sysLog);
//         new Thread(){//少量请求这样写没有问题
//               @Override
//               public void run() {
//                   sysLogService.insertLog(sysLog);
//               }
//           }.start();
       }
       @Autowired
       private SysLogService sysLogService;

}
